<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2012 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
// $Id: RestAction.class.php 2795 2012-03-02 15:34:18Z liu21st $


/**
 +------------------------------------------------------------------------------
 * ThinkPHP RESTFul 控制器扩展类
 +------------------------------------------------------------------------------
 */
abstract class RestAction {
	
	// 当前Action名称
	private $name = '';
	// 视图实例
	protected $view = null;
	protected $_method = ''; // 当前请求类型
	protected $_type = ''; // 当前资源类型
	// 输出类型
	protected $_types = array ();
	
	/**
     +----------------------------------------------------------
	 * 架构函数 取得模板对象实例
     +----------------------------------------------------------
	 * @access public
     +----------------------------------------------------------
	 */
	public function __construct() {
		//实例化视图类
		$this->view = Think::instance ( 'View' );
		
		if (! defined ( '__EXT__' ))
			define ( '__EXT__', '' );
		
		// 资源类型检测
		if ('' == __EXT__) { // 自动检测资源类型
			$this->_type = $this->getAcceptType ();
		} elseif (false === stripos ( C ( 'REST_CONTENT_TYPE_LIST' ), __EXT__ )) {
			// 资源类型非法 则用默认资源类型访问
			$this->_type = C ( 'REST_DEFAULT_TYPE' );
		} else {
			// 检测实际资源类型
			if ($this->getAcceptType () == __EXT__) {
				$this->_type = __EXT__;
			} else {
				$this->_type = C ( 'REST_DEFAULT_TYPE' );
			}
		}
		
		// 请求方式检测
		$method = strtolower ( $_SERVER ['REQUEST_METHOD'] );
		if (false === stripos ( C ( 'REST_METHOD_LIST' ), $method )) {
			// 请求方式非法 则用默认请求方法
			$method = C ( 'REST_DEFAULT_METHOD' );
		}
		$this->_method = $method;
		// 允许输出的资源类型
		$this->_types = C ( 'REST_OUTPUT_TYPE' );
		
		//控制器初始化
		if (method_exists ( $this, '_initialize' ))
			$this->_initialize ();
	}
	
	/**
     +----------------------------------------------------------
	 * 获取当前Action名称
     +----------------------------------------------------------
	 * @access protected
     +----------------------------------------------------------
	 */
	protected function getActionName() {
		if (empty ( $this->name )) {
			// 获取Action名称
			$this->name = substr ( get_class ( $this ), 0, - 6 );
		}
		return $this->name;
	}
	
	/**
     +----------------------------------------------------------
	 * 是否AJAX请求
     +----------------------------------------------------------
	 * @access protected
     +----------------------------------------------------------
	 * @return bool
     +----------------------------------------------------------
	 */
	protected function isAjax() {
		if (isset ( $_SERVER ['HTTP_X_REQUESTED_WITH'] )) {
			if ('xmlhttprequest' == strtolower ( $_SERVER ['HTTP_X_REQUESTED_WITH'] ))
				return true;
		}
		if (! empty ( $_POST [C ( 'VAR_AJAX_SUBMIT' )] ) || ! empty ( $_GET [C ( 'VAR_AJAX_SUBMIT' )] ))
			// 判断Ajax方式提交
			return true;
		return false;
	}
	
	/**
     +----------------------------------------------------------
	 * 魔术方法 有不存在的操作的时候执行
     +----------------------------------------------------------
	 * @access public
     +----------------------------------------------------------
	 * @param string $method 方法名
	 * @param array $args 参数
     +----------------------------------------------------------
	 * @return mixed
     +----------------------------------------------------------
	 */
	public function __call($method, $args) {
		if (0 === strcasecmp ( $method, ACTION_NAME )) {
			if (method_exists ( $this, $method . '_' . $this->_method . '_' . $this->_type )) { // RESTFul方法支持
				$fun = $method . '_' . $this->_method . '_' . $this->_type;
				$this->$fun ();
			} elseif ($this->_method == C ( 'REST_DEFAULT_METHOD' ) && method_exists ( $this, $method . '_' . $this->_type )) {
				$fun = $method . '_' . $this->_type;
				$this->$fun ();
			} elseif ($this->_type == C ( 'REST_DEFAULT_TYPE' ) && method_exists ( $this, $method . '_' . $this->_method )) {
				$fun = $method . '_' . $this->_method;
				$this->$fun ();
			} elseif (method_exists ( $this, '_empty' )) {
				// 如果定义了_empty操作 则调用
				$this->_empty ( $method, $args );
			} elseif (file_exists_case ( C ( 'TMPL_FILE_NAME' ) )) {
				// 检查是否存在默认模版 如果有直接输出模版
				$this->display ();
			} else {
				// 抛出异常
				throw_exception ( L ( '_ERROR_ACTION_' ) . ACTION_NAME );
			}
		} else {
			switch (strtolower ( $method )) {
				// 获取变量 支持过滤和默认值 调用方式 $this->_post($key,$filter,$default);
				case '_get' :
					$input = & $_GET;
					break;
				case '_post' :
					$input = & $_POST;
					break;
				case '_put' :
				case '_delete' :
					parse_str ( file_get_contents ( 'php://input' ), $input );
					break;
				case '_request' :
					$input = & $_REQUEST;
					break;
				case '_session' :
					$input = & $_SESSION;
					break;
				case '_cookie' :
					$input = & $_COOKIE;
					break;
				case '_server' :
					$input = & $_SERVER;
					break;
				default :
					throw_exception ( __CLASS__ . ':' . $method . L ( '_METHOD_NOT_EXIST_' ) );
			}
			if (isset ( $input [$args [0]] )) { // 取值操作
				$data = $input [$args [0]];
				$fun = $args [1] ? $args [1] : C ( 'DEFAULT_FILTER' );
				$data = $fun ( $data ); // 参数过滤
			} else { // 变量默认值
				$data = isset ( $args [2] ) ? $args [2] : NULL;
			}
			return $data;
		}
	}
	
	/**
     +----------------------------------------------------------
	 * 模板显示
	 * 调用内置的模板引擎显示方法，
     +----------------------------------------------------------
	 * @access protected
     +----------------------------------------------------------
	 * @param string $templateFile 指定要调用的模板文件
	 * 默认为空 由系统自动定位模板文件
	 * @param string $charset 输出编码
	 * @param string $contentType 输出类型
     +----------------------------------------------------------
	 * @return void
     +----------------------------------------------------------
	 */
	protected function display($templateFile = '', $charset = '', $contentType = '') {
		$this->view->display ( $templateFile, $charset, $contentType );
	}
	
	/**
     +----------------------------------------------------------
	 * 模板变量赋值
     +----------------------------------------------------------
	 * @access protected
     +----------------------------------------------------------
	 * @param mixed $name 要显示的模板变量
	 * @param mixed $value 变量的值
     +----------------------------------------------------------
	 * @return void
     +----------------------------------------------------------
	 */
	protected function assign($name, $value = '') {
		$this->view->assign ( $name, $value );
	}
	
	public function __set($name, $value) {
		$this->view->assign ( $name, $value );
	}
	
	/**
     +----------------------------------------------------------
	 * 设置页面输出的CONTENT_TYPE和编码
     +----------------------------------------------------------
	 * @access public
     +----------------------------------------------------------
	 * @param string $type content_type 类型对应的扩展名
	 * @param string $charset 页面输出编码
     +----------------------------------------------------------
	 * @return void
     +----------------------------------------------------------
	 */
	public function setContentType($type, $charset = '') {
		if (headers_sent ())
			return;
		if (empty ( $charset ))
			$charset = C ( 'DEFAULT_CHARSET' );
		$type = strtolower ( $type );
		if (isset ( $this->_types [$type] )) //过滤content_type
			header ( 'Content-Type: ' . $this->_types [$type] . '; charset=' . $charset );
	}
	
	/**
     +----------------------------------------------------------
	 * 输出返回数据
     +----------------------------------------------------------
	 * @access protected
     +----------------------------------------------------------
	 * @param mixed $data 要返回的数据
	 * @param String $type 返回类型 JSON XML
	 * @param integer $code HTTP状态
     +----------------------------------------------------------
	 * @return void
     +----------------------------------------------------------
	 */
	protected function response($data, $type = '', $code = 200) {
		// 保存日志
		if (C ( 'LOG_RECORD' ))
			Log::save ();
		$this->sendHttpStatus ( $code );
		exit ( $this->encodeData ( $data, strtolower ( $type ) ) );
	}
	
	/**
     +----------------------------------------------------------
	 * 编码数据
     +----------------------------------------------------------
	 * @access protected
     +----------------------------------------------------------
	 * @param mixed $data 要返回的数据
	 * @param String $type 返回类型 JSON XML
     +----------------------------------------------------------
	 * @return void
     +----------------------------------------------------------
	 */
	protected function encodeData($data, $type = '') {
		if (empty ( $data ))
			return '';
		if ('json' == $type) {
			// 返回JSON数据格式到客户端 包含状态信息
			$data = json_encode ( $data );
		} elseif ('xml' == $type) {
			// 返回xml格式数据
			$data = xml_encode ( $data );
		} elseif ('php' == $type) {
			$data = serialize ( $data );
		} // 默认直接输出
		$this->setContentType ( $type );
		header ( 'Content-Length: ' . strlen ( $data ) );
		return $data;
	}
	
	// 发送Http状态信息
	protected function sendHttpStatus($status) {
		static $_status = array (// Informational 1xx
		100 => 'Continue', 101 => 'Switching Protocols', // Success 2xx
		200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', // Redirection 3xx
		300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Moved Temporarily ', // 1.1
303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', // 306 is deprecated but reserved
		307 => 'Temporary Redirect', // Client Error 4xx
		400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Timeout', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Long', 415 => 'Unsupported Media Type', 416 => 'Requested Range Not Satisfiable', 417 => 'Expectation Failed', // Server Error 5xx
		500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Timeout', 505 => 'HTTP Version Not Supported', 509 => 'Bandwidth Limit Exceeded' );
		if (isset ( $_status [$code] )) {
			header ( 'HTTP/1.1 ' . $code . ' ' . $_status [$code] );
			// 确保FastCGI模式下正常
			header ( 'Status:' . $code . ' ' . $_status [$code] );
		}
	}
	/**
     +----------------------------------------------------------
	 * 获取当前请求的Accept头信息
     +----------------------------------------------------------
	 * @return string
     +----------------------------------------------------------
	 */
	protected function getAcceptType() {
		$type = array ('html' => 'text/html,application/xhtml+xml,*/*', 'xml' => 'application/xml,text/xml,application/x-xml', 'json' => 'application/json,text/x-json,application/jsonrequest,text/json', 'js' => 'text/javascript,application/javascript,application/x-javascript', 'css' => 'text/css', 'rss' => 'application/rss+xml', 'yaml' => 'application/x-yaml,text/yaml', 'atom' => 'application/atom+xml', 'pdf' => 'application/pdf', 'text' => 'text/plain', 'png' => 'image/png', 'jpg' => 'image/jpg,image/jpeg,image/pjpeg', 'gif' => 'image/gif', 'csv' => 'text/csv' );
		
		foreach ( $type as $key => $val ) {
			$array = explode ( ',', $val );
			foreach ( $array as $k => $v ) {
				if (stristr ( $_SERVER ['HTTP_ACCEPT'], $v )) {
					return $key;
				}
			}
		}
		return false;
	}
}